module ActiveResource #:nodoc:
  class Base #:nodoc:
    class << self
      def belongs_to(association_id, options = {})
        singular = association_id.to_s
        plural   = association_id.to_s.pluralize

        options[:class_name]  ||= absolute_name_for(singular.camelize)
        options[:foreign_key] ||= singular.foreign_key

        src = <<-end_src
          class << self
            def path_prefix
              (#{options[:class_name]}.respond_to?(:path_prefix) ? #{options[:class_name]}.path_prefix : '') + '/#{plural}/:#{options[:foreign_key]}'
            end
          end

          def #{singular}
            raise "Filter ID in except below" if prefix_options.has_key?(:id)  # XXX: DEBUG
            @#{singular} ||= #{options[:class_name]}.find #{options[:foreign_key]}, :params => prefix_options.except(:#{options[:foreign_key]})
          end
        end_src
        module_eval src, __FILE__, __LINE__
      end

      def has_many(association_id, options = {})
        singular = association_id.to_s.singularize
        plural   = association_id.to_s

        options[:class_name]  ||= absolute_name_for(singular.camelize)
        options[:foreign_key] ||= self.to_s.demodulize.foreign_key

        src = <<-end_src
          def #{plural}
            @#{plural} ||= AssociationProxy.new(#{options[:class_name]}, prefix_options.merge(:#{options[:foreign_key]} => id))
          end
        end_src
        module_eval src, __FILE__, __LINE__
      end

      private
        def absolute_name_for(class_name)
          "#{self.to_s.sub(/::\w+\z/, '')}::#{class_name}"
        end
    end

    def attributes=(new_attributes)
      return if new_attributes.nil?
      new_attributes.stringify_keys.each do |attribute,value|
        send "#{attribute}=", value
      end
    end

    # TODO: patch Rails so that the prefix_options are merged
    #       in with the response
    def load_attributes_from_response(response)
      if response['Content-size'] != '0' && response.body.strip.size > 0
        load(prefix_options.merge(self.class.format.decode(response.body)))
      end
    end

    def method_missing_with_prefix_options(method_symbol, *args, &block)
      method_name = method_symbol.to_s.match(/\A(\w+)(?:\?)?\z/).captures.first.to_sym rescue nil

      if method_name && prefix_options.has_key?(method_name)
        prefix_options[method_name]
      else
        method_missing_without_prefix_options method_symbol, *args, &block
      end
    end

    alias_method_chain :method_missing, :prefix_options
  end

  class AssociationProxy #:nodoc:
    attr_reader :klass

    def initialize(klass, params)
      @klass, @params = klass, params
      @target, @loaded = [], false
    end

    def create(attributes)
      returning klass.create(@params.merge(attributes)) do |resource|
        target << resource
      end
    end

    def find(*args)
      options = { :params => {} }.update(args.extract_options!)
      args << options.merge(:params => @params.merge(options[:params]))
      klass.find(*args)
    end

    private
      def target
        return @target if @loaded
        @target = returning klass.find(:all, :params => @params) do
          @loaded = true
        end
      end

      def method_missing(method, *args, &block)
        target.send(method, *args, &block)
      end
  end
end
